<?php
/**
 * Created by PhpStorm.
 * User: lulanyin@qq.com
 * DateTime: 2016/1/13 11:30
 * Web: http://lanyin.lu
 * 操作数组、StdClass对象
 */
class QueryToObject {
    private $object;    //要操作的数据对象
    private $select;    //要查询的字段
    private $wheres;     //条件
    private $orderBy;   //排序条件
    //符号集
    public $operators = array(
        '=', '==','===', '<', '>', '<=', '>=', '!=','!==',
        'like', 'not like', 'between', 'not between',
        'value like', 'value not like',//反过来匹配
        'in','not in','find in set','not find in set',
        'value in', 'value not in', 'value find in set', 'value not find in set'//反过来匹配
    );

    /**
     * QueryToObject constructor.
     * @param QueryToSQL|array|object $object
     * @return QueryToObject
     */
    public function __construct($object){
        if(!is_array(end($object)) && !is_object(end($object))){
            exit('must be a array list');
        }
        $object = $object instanceof QueryToSQL ? $object->toArray() : $object;
        $this->object = $object;
        return $this;
    }

    /**
     * 新起一个对象
     * @return QueryToObject
     */
    public function newQuery(){
        return new QueryToObject($this->object);
    }

    /**
     * 设置查询字段
     * @param array $columns
     * @return QueryToObject
     */
    public function select($columns=array('*')){
        $this->select = is_array($columns) ? $columns : func_get_args();
        return $this;
    }

    /**
     * 查询条件
     * @param $column
     * @param $operator
     * @param $value
     * @param string $boolean
     * @return QueryToObject
     */
    public function where($column, $operator=null, $value=null, $boolean='and'){
        if(is_array($column)){
            //数组条件
            foreach($column as $c){
                $col = $c[0];
                $ope = isset($c[1]) ? $c[1] : null;
                $val = isset($c[2]) ? $c[2] : null;
                $bool = isset($c[3]) ? $c[3] : 'and';
                $this->where($col, $ope, $val, $bool);
            }
            return $this;
        }elseif($column instanceof Closure){
            $query = $this->newQuery();
            call_user_func($column, $query);
            $this->object = $query->get();
            return $this;
        }else{
            if(func_num_args()==2){
                list($value, $operator) = array($operator, "=");
            }elseif(!$this->invalidOperatorAndValue($operator)){
                throw new InvalidArgumentException("Value must be provided.");
            }
            $this->wheres[] = compact('column', 'operator', 'value', 'boolean');
            return $this;
        }
    }

    /**
     * IN筛选
     * @param $column
     * @param $value
     * @param string $boolean
     * @return QueryToObject
     */
    public function whereIn($column, $value, $boolean='and'){
        return $this->where($column, 'in', $value, $boolean);
    }
    public function whereNotIn($column, $value, $boolean='and'){
        return $this->where($column, 'not in', $value, $boolean);
    }
    public function whereValueIn($column, $value, $boolean='and'){
        return $this->where($column, 'value in', $value, $boolean);
    }
    public function whereValueNotIn($column, $value, $boolean='and'){
        return $this->where($column, 'value not in', $value, $boolean);
    }

    /**
     * LIKE筛选
     * @param $column
     * @param $value
     * @param string $boolean
     * @return QueryToObject
     */
    public function whereLike($column, $value, $boolean='and'){
        return $this->where($column, 'like', $value, $boolean);
    }
    public function whereNotLike($column, $value, $boolean='and'){
        return $this->where($column, 'not like', $value, $boolean);
    }
    public function whereValueLike($column, $value, $boolean='and'){
        return $this->where($column, 'value like', $value, $boolean);
    }
    public function whereValueNotLike($column, $value, $boolean='and'){
        return $this->where($column, 'value not like', $value, $boolean);
    }

    /**
     * BETWEEN
     * @param $column
     * @param $value
     * @param string $boolean
     * @return QueryToObject
     */
    public function whereBetween($column, $value, $boolean='and'){
        return $this->where($column, 'between', $value, $boolean);
    }
    public function whereNotBetween($column, $value, $boolean='and'){
        return $this->where($column, 'not between', $value, $boolean);
    }
    public function whereValueBetween($column, $value, $boolean='and'){
        return $this->where($column, 'value between', $value, $boolean);
    }
    public function whereValueNotBetween($column, $value, $boolean='and'){
        return $this->where($column, 'value not between', $value, $boolean);
    }

    /**
     * FIND IN SET
     * @param $column
     * @param $value
     * @param string $boolean
     * @return QueryToObject
     */
    public function whereFindInSet($column, $value, $boolean='and'){
        return $this->where($column, 'find in set', $value, $boolean);
    }
    public function whereNotFindInSet($column, $value, $boolean='and'){
        return $this->where($column, 'not find in set', $value, $boolean);
    }
    public function whereValueFindInSet($column, $value, $boolean='and'){
        return $this->where($column, 'value find in set', $value, $boolean);
    }
    public function whereValueNotFindInSet($column, $value, $boolean='and'){
        return $this->where($column, 'value not find in set', $value, $boolean);
    }

    /**
     * 验证连接对比符号
     * @param $operator
     * @return bool
     */
    protected function invalidOperatorAndValue($operator){
        return in_array($operator, $this->operators);
    }

    /**
     * 进行对比
     * @param $var
     * @param $operator
     * @param $value
     * @return mixed
     */
    protected function contrast($var, $operator, $value){
        switch($operator){
            case "in" :
                $value = is_array($value) ? $value : explode(',', $value);
                return in_array($var, $value);
                break;
            case "not in" :
                $value = is_array($value) ? $value : explode(',', $value);
                return !in_array($var, $value);
                break;
            case "value in" :
                return $this->contrast($value, 'in', $var);
                break;
            case "value not in" :
                return $this->contrast($value, 'value not in', $var);
                break;
            case "like" :
                return strripos($var, $value)>=0;
                break;
            case "not like" :
                return strripos($var, $value)<0;
                break;
            case "value like" :
                return strripos($value, $var)>=0;
                break;
            case "value not like" :
                return strripos($value, $var)<0;
                break;
            case "between" :
                if(!is_array($value)){
                    $min = isset($value[0]) && is_numeric($value[0]) ? $value[0] : 0;
                    $max = isset($value[1]) && is_numeric($value[1]) ? $value[1] : 999;
                    return $var>=$min && $value<=$max;
                }
                return false;
                break;
            case "not between" :
                if(!is_array($value)){
                    $min = isset($value[0]) && is_numeric($value[0]) ? $value[0] : 0;
                    $max = isset($value[1]) && is_numeric($value[1]) ? $value[1] : 999;
                    return !$var>=$min || !$value<=$max;
                }
                return false;
                break;
            case "find in set" :
                return $this->contrast($var, 'in', $value);
                break;
            case "not find in set" :
                return $this->contrast($var, 'not in', $value);
                break;
            case "value find in set" :
                return $this->contrast($value, 'in', $var);
                break;
            case "value not find in set" :
                return $this->contrast($value, 'in', $var);
                break;
            default :
                return eval('return $var'.$operator.'$value;');
                break;
        }
    }

    /**
     *
     * @param array $obj
     * @return bool
     */
    protected function filter($obj){
        $wheres = $this->wheres;
        $bool_list = array();
        $bool = true;
        if(!empty($wheres)){
            foreach($wheres as $w){
                $operator = $w['operator'];
                switch($operator){
                    case "=" :
                        $operator = "==";
                        break;
                }
                if(is_array($obj)){
                    $col = isset($obj[$w['column']]) ? $obj[$w['column']] : '';
                }else{
                    $col = isset($obj->$w['column']) ? $obj->$w['column'] : '';
                }
                $val = $w['value'];
                $bool = $this->contrast($col, $operator, $val);
                $bool_list[] = array(
                    'bool' => $bool,
                    'boolean' => $w['boolean']
                );
            }
        }
        return $bool;//暂时不支持条件分区
    }

    /**
     * 获取筛选结果
     * @param null $total
     * @param int $offset
     * @return array
     */
    public function get($total=null, $offset=0){
        $total = !is_numeric($total) || $total===null ? count($this->object) : $total;
        $data = array();
        $i=0;
        foreach($this->object as $key=>$obj){
            if($this->filter($obj) && $key>=$offset){
                $data[] = $obj;
                $i++;
                if($i==$total){
                    return $data;
                }
            }
        }
        return $data;
    }

    /**
     * 取得筛选结果中的一条数据
     * @return array
     */
    public function first(){
        $data = $this->get(1);
        return !empty($data) ? end($data) : array();
    }

    /**
     * 获取筛选结果的键值
     * @param $column
     * @return string
     */
    public function pluck($column){
        $firstRow = $this->first();
        if(is_array($this->object)){
            return isset($firstRow->$column) ? $firstRow->$column : "";
        }
        return isset($firstRow[$column]) ? $firstRow[$column] : "";
    }

    /**
     * 获取筛选结果键值列表
     * @param $column
     * @param bool|false $distinct
     * @return array
     */
    public function lists($column, $distinct=false){
        $rows = $this->get();
        $list = array();
        foreach($rows as $r){
            if(is_array($this->object)){
                if(isset($r[$column])){
                    $list[] = $r[$column];
                }
            }else{
                if(isset($r->$column)){
                    $list[] = $r->$column;
                }
            }
        }
        return $distinct ? array_unique($list) : $list;
    }

    /**
     * 数组元素之和
     * @param $column
     * @return int|number
     */
    public function sum($column){
        $list = $this->lists($column);
        return !empty($list) ? array_sum($list) : 0;
    }
}